﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Data.SqlClient;
using System.Data.SqlTypes;

using Noris.Schedule.Support;
using Noris.Schedule.Support.Data;
using Noris.Schedule.Support.Sql;
using Noris.Schedule.Support.Core;
using Noris.Schedule.Support.DataFace;

using Noris.Schedule.Planning.DataFace;
using System.Drawing;

// Tento soubor obsahuje rozšíření třídy CapacityDataCls o metody pro Load & Save
namespace Noris.Schedule.Planning.ProcessData
{
	#region CLASS CapacityDataCls : ucelený subsystém zdrojů kapacit, řeší obecné kapacitní zdroje a alternaci kapacitních jednotek
	/// <summary>
	/// CapacityDataCls : Subsystém zdrojů kapacit, řeší obecné kapacitní zdroje a alternaci kapacitních jednotek
	/// </summary>
	public partial class CapacityDataCls : PlanningComponentCls, IDisposable
	{
		#region ČTENÍ DAT PLÁNU Z DATABÁZE
		/// <summary>
		/// Metoda zajistí načtení svých plánovacích dat z databáze.
		/// Nenačítají se číselníky ani definice, protože v této době (DataLoad()) jsou již číselníky načteny 
		/// (metoda DataPrepare() již  proběhla).
		/// Neprovádí se DataClear, protože to proběhlo centrálně před DataLoad (viz PlanningDataCls.DataLoad()).
		/// Načítají se stavy kapacit atd, tedy to, co se ukládá v DataSave().
		/// </summary>
		internal override void DataLoad()
		{
			// Technika: data jsou připravena (vytvořena, vyčištěna, číselníky načteny).
			//   Načteme stavy kapacit a založíme je do kapacitních jednotek, přičemž budou setříděny, indexovány a vytvořeny mapy stavů kapacit po periodách,
			//   pak načteme kapacitní linky a vložíme je do odpovídajících kapacitních jednotek a stavů kapacit.
			this._WorkLoadIndex = new Dictionary<GID, GID>();
			
			this._DataLoadCapacityLevel();
		}
		/// <summary>
		/// Ukončí režim vstupu dat z databáze.
		/// Uvolní pracovní dočasná data, načtená do paměti v průběhu metody DataLoad().
		/// Tato data slouží k umožnění křížového dohledání záznamů i z jiných modulů.
		/// </summary>
		internal void DataLoadEnd()
		{
			foreach (CapacityUnitCls unit in this._UnitDict.Values)
				unit.DataLoadEnd();

			this._WorkLoadIndex = null;
		}
		/// <summary>
		/// Načte stavy kapacit plan_unit_c_cl do paměti kapacitních jednotek
		/// </summary>
		private void _DataLoadCapacityLevel()
		{
            using (var scope = Steward.TraceScopeBegin(Constants.TraceTypeCapacity, "Load.Level", Constants.TraceKeyWordAplScheduler))
            {
                // scope.SuppressInnerTrace(null, null, "SQL");

                string sql;

                // Stavy kapacit:
                sql = PlanUnitCClCls.SqlSelect + " WHERE plan_c_version = " + SqlFormat.ToSql(this.ParentPlanning.PlanningParameters.PlanCVersion);
                List<PlanUnitCClCls> cclList = Db_Layer.GetList<PlanUnitCClCls>(sql);
                foreach (PlanUnitCClCls ccl in cclList)
                {
                    CapacityUnitCls unit = this.GetCapacityUnit(ccl.PlanUnitC);
                    if (unit == null) continue;

                    GID levelGId = unit.DataLoadAddCCl(ccl);              // Do jednotky vložím stav kapacit, vrátí se mi jeho paměťový GID
                    if (levelGId.IsEmpty) continue;

                    this._WorkLoadIndex.Add(ccl.GId, levelGId);           // Index: pro klíč záznamu (CCL) z databáze uložím klíč záznamu v paměti
                    this._WorkLoadIndex.Add(levelGId, unit.PlanUnitGID);  // Index: pro klíč Level (paměťový objekt) uložím klíč plánovací jednotky pro pozdější dohledání
                }
                int cclCnt = cclList.Count;
                cclList.Clear();
                cclList = null;

                // Pracovní linky (Desk):
                sql = PlanUnitCDeskCls.SqlSelect + " WHERE plan_unit_c_cl in (select cislo_nonsubjektu from lcs.plan_unit_c_cl where plan_c_version = " + SqlFormat.ToSql(this.ParentPlanning.PlanningParameters.PlanCVersion) + ")";
                List<PlanUnitCDeskCls> ccdList = Db_Layer.GetList<PlanUnitCDeskCls>(sql);
                foreach (PlanUnitCDeskCls ccd in ccdList)
                {	// Pro každý Desk najdu jeho CCl (záznam Desk se váže na CCl, a teprve CCl se váže na Unit):
                    CapacityUnitCls unit = this._DataLoadGetUnitByCCl(ccd.PlanUnitCCl);
                    if (unit == null) continue;

                    // Do Unit vložím další Desk:
                    GID deskGId = unit.DataLoadAddDesk(ccd);
                    if (deskGId.IsEmpty) continue;

                    GID levelGId = this._DataLoadGetLevelGIdByCCl(ccd.PlanUnitCCl);   // Najdu GID záznamu Level v paměti na základě záznamu CCL v databázi
                    if (levelGId.IsEmpty) continue;

                    this._WorkLoadIndex.Add(ccd.GId, deskGId);           // Index: pro klíč záznamu (Desk) z databáze uložím klíč záznamu v paměti
                    this._WorkLoadIndex.Add(deskGId, levelGId);           // Index: pro klíč Desk (paměťový objekt) uložím klíč stavu kapacit v paměti LevelGID pro pozdější dohledání
                }
                int ccdCnt = ccdList.Count;
                ccdList.Clear();
                ccdList = null;

                scope.User = new string[]
                { 
                    "Level.Count = " + cclCnt.ToString(),
                    "Desk.Count = " + ccdCnt.ToString()
                };
            }

            this._DataLoadCapacityLevelFinalize();
        }
        private void _DataLoadCapacityLevelFinalize()
        {
            using (var scope = Steward.TraceScopeBegin(Constants.TraceTypeCapacity, "Load.Level.Finalize", Constants.TraceKeyWordAplScheduler))
            {
                // Provedeme finalizaci dat = sestavení stavů kapacit v jednotlivých Unit do permanentních Dictionary a jejich očíslování a tvorbu period:
                foreach (CapacityUnitCls unit in this._UnitDict.Values)
                    unit.DataLoadAddFinish(this.Auditor);
            }
		}
		/// <summary>
		/// Metoda najde a vrátí GID záznamu Level v paměti na základě čísla nonsubjektu CCL
		/// </summary>
		/// <param name="cclKey">Klíč záznamu PlanUnitCCl (stav kapacit z databáze)</param>
		/// <returns></returns>
		private GID _DataLoadGetLevelGIdByCCl(SqlInt32 cclKey)
		{
			if (cclKey.IsNull) return GID.Empty;

			// Podle klíče stavu kapacit z databáze (cclKey, třída plan_unit_c_cl) najdu klíč Level v paměti (za pomoci indexu):
			GID cclDbGId = new GID(PlanUnitCClCls.ClassNr, cclKey.Value);
			GID levelGId;
			if (!this._WorkLoadIndex.TryGetValue(cclDbGId, out levelGId)) return GID.Empty;
			return levelGId;
		}
		/// <summary>
		/// Metoda najde a vrátí plánovací jednotku (její data) na základě klíče záznamu CCL.
		/// V době načítání dat (DataLoad) jsou naplněny interní registry, obsahují data CCl a Desk, podle nich lze najít navázaná data.
		/// Podle klíče CCl lze najít záznam CCl, a v něm lze najít klíč PlanUnitC, a tu Unit pak najdeme a vrátíme.
		/// Pokud něco nenajdeme, vrací se null.
		/// </summary>
		/// <param name="cclKey">Klíč záznamu PlanUnitCCl (stav kapacit z databáze)</param>
		/// <returns></returns>
		private CapacityUnitCls _DataLoadGetUnitByCCl(SqlInt32 cclKey)
		{
			// Podle klíče stavu kapacit z databáze (cclKey, třída plan_unit_c_cl) najdu klíč Level v paměti (za pomoci indexu):
			GID levelGId = _DataLoadGetLevelGIdByCCl(cclKey);
			if (levelGId.IsEmpty) return null;

			// Podle klíče záznamu Level v paměti najdu klíč kapacitní jednotky:
			GID unitGId;
			if (!this._WorkLoadIndex.TryGetValue(levelGId, out unitGId)) return null;

			// Vrátím data kapacitní jednotky:
			return this.GetCapacityUnit(unitGId);
		}
		/// <summary>
		/// Metoda najde a vrátí objekt CapacityDeskCls (paměťový), 
		/// který odpovídá danému číslu záznamu (lcs.plan_unit_c_desk.cislo_nonsubjektu).
		/// </summary>
		/// <param name="planUnitCDesk"></param>
		/// <returns></returns>
		internal CapacityDeskCls DataLoadWorkFindDesk(SqlInt32 planUnitCDesk)
		{
			if (planUnitCDesk.IsNull) return null;

			// Nejprve najdu GID paměťového záznamu Desk:
			GID ccdDbGId = new GID(PlanUnitCDeskCls.ClassNr, planUnitCDesk.Value);
			GID deskGId;
			if (!this._WorkLoadIndex.TryGetValue(ccdDbGId, out deskGId)) return null;

			// Pak pro Desk v paměti najdu jeho stav kapacit Level v paměti:
			GID levelGId;
			if (!this._WorkLoadIndex.TryGetValue(deskGId, out levelGId)) return null;

			// Pak pro Leve najdu Unit:
			GID unitGId;
			if (!this._WorkLoadIndex.TryGetValue(levelGId, out unitGId)) return null;

			// Pak najdu vlastní data Unit:
			CapacityUnitCls unit = this.GetCapacityUnit(unitGId);
			if (unit == null) return null;

			// A pak požádám Unit, ať mi vrátí Desk:
			return unit.SeekDesk(levelGId, deskGId);
		}
		/// <summary>
		/// Pracovní index při načítání dat plánu z databáze do paměti.
		/// Klíčem je GID záznamu v databázi, hodnotou je klíč GID téhož záznamu v interní datové struktuře.
		/// Současně obsahuje klíče GID záznamů v paměti (jejichž číslo třídy je jiné, než číslo třídy dat z databáze),
		/// hodnotou pro tyto záznamy je klíč GID nadřízeného prvku v paměti (v hierarchii: Unit - Time - Pass - Task - Axis)
		/// </summary>
		private Dictionary<GID, GID> _WorkLoadIndex;
		#endregion
		#region UKLÁDÁNÍ DAT PLÁNU DO DATABÁZE
		/// <summary>
		/// Metoda zajistí uložení svých dat do databáze.
		/// Pozor: tato metoda neukládá data o vztahu tasků a stavů kapacit do tabulky lcs.plan_unit_c_cl_rel, 
		/// to řeší metoda SaveDataCClRel(), kterou je ale nutno volat až po uložení dat AxisHeap (kde se určují čísla tasků).
		/// </summary>
		internal override void DataSave()
		{
			this._DataSaveDeleteOldRecords();
			this._DataSaveCapacityUnit();
		}
		/// <summary>
		/// Metoda sestaví a uloží data do tabulky lcs.plan_unit_c_cl_rel, o vztahu mezi taskem a stavem kapacit.
		/// Jde o sumární (sekundární) záznam, který je určen součtem všech dat v CapacityLevel.Desk pro každý Task.
		/// Tuto metodu je nutno volat až po volání metody this.AxisHeap.SaveData(), protože je nutné mít již určená čísla záznamů tasků.
		/// </summary>
		internal void DataSaveCClRel()
		{
			this._DataSaveCClRel();
		}
		/// <summary>
		/// Smaže všechny záznamy z databáze, které se týkají aktuální verze plánu, a které obhospodařuje tento modul.
		/// Jde tedy o stavy kapacit a kapacitní linky.
		/// </summary>
		private void _DataSaveDeleteOldRecords()
		{
            using (var scope = Steward.TraceScopeBegin(Constants.TraceTypeCapacity, "DeleteOldRecords", Constants.TraceKeyWordAplScheduler))
            {
                // Smazání se provede jedním SQL příkazem, který využije temp tabulku pro uložení čísel záznamů stavů kapacit (c_cl),
                //   které se budou mazat (na základě čísla verze plánu):
                string sql = @"-- Delete all capacity records for plan version {{PlanCVersion}}

-- temp table :
IF EXISTS (select * from tempdb.sys.tables where left(name,14) = '#plan_records_')
	DROP TABLE #plan_records

CREATE TABLE #plan_records (cislo_nonsubjektu int not null)
CREATE INDEX #plan_records_idx1 ON #plan_records (cislo_nonsubjektu)

-- Part:
-- delete capacity_levels:
INSERT INTO #plan_records SELECT cislo_nonsubjektu FROM lcs.plan_unit_c_cl WHERE ISNULL(plan_c_version,0) = {{PlanCVersion}}

-- Part:
DELETE FROM lcs.plan_unit_c_cl_rel WHERE plan_unit_c_cl in (select cislo_nonsubjektu from #plan_records)
-- Part:
DELETE FROM lcs.plan_unit_c_desk WHERE plan_unit_c_cl in (select cislo_nonsubjektu from #plan_records)
-- Part:
DELETE FROM lcs.plan_unit_c_cl WHERE cislo_nonsubjektu in (select cislo_nonsubjektu from #plan_records)

-- Final:
DROP TABLE #plan_records
";

                sql = sql.Replace("{{PlanCVersion}}", this.ParentPlanning.PlanningParameters.PlanCVersion.ToString());

                // DAJ 0044791 2013-05-20: na velkých DB je příkaz k mazání příliš dlouhotrvající, a protože běží pod jedním TimeOutem, pak někdy skončí chybou Timeout.
                // Proto byla vytvořena metoda Db_Layer.ExecuteNonQueryPart(), která daný SQL příkaz rozdělí v řádcích -- Part: a -- Final:
                sql = sql.Replace("-- Part:", Db_Layer.SQL_PART_ITEM);
                sql = sql.Replace("-- Final:", Db_Layer.SQL_PART_FINAL);
                Db_Layer.ExecuteNonQueryPart(sql, 300);

                // Po provedení výmazu z databáze musím smazat čísla RecordNumber v záznamech v paměti,
                //  tím zajistím při jejich ukládání režim INSERT, a ne UPDATE.
                foreach (CapacityUnitCls unit in this._UnitDict.Values)
                    unit.SaveNullRecordNumber();
            }
		}
		/// <summary>
		/// Metoda uloží data o stavech kapacit za svoje KPJ, a o jejich Desk.
		/// Do stavů kapacit i do Desk ukládá jejich využití.
		/// Pozn.: tato metoda neukládá záznamy CClRel = vztahy mezi taskem a stavem kapacit.
		/// Pro korektní uložení tohoto záznamu je nutné znát klíč jak záznamu tasku, tak záznamu CCl.
		/// Tento vztah se ukládá až na konci techniky ukládání dat, protože pořadí ukládání modulů je:
		///  1.Kapacity, 
		///  2.Materiál,
		///  3.Osa S a C tasky
		///  4.Vztahy CClRel
		///  5.Operace výrobních příkazů (časy, fixace)
		/// </summary>
		private void _DataSaveCapacityUnit()
		{
			// Nejprve určíme časový rozsah pro stavy kapacit = krajní meze časů, kdy jsou nějaké stavy kapacit použité:
			IntRange usedPeriods = _DataSaveGetUsedPeriods();
			if (usedPeriods.IsEmpty) return;            // Není co ukládat...

			// Vlastní ukládání dat:
            using (var scope = Steward.TraceScopeBegin(Constants.TraceTypeCapacity, "Save.Unit", Constants.TraceKeyWordAplScheduler, "Unit.Count = " + this._UnitDict.Count.ToString()))
            {
                scope.SuppressInnerTrace(null, null, "SQL");
                int levelCount = 0;
                foreach (CapacityUnitCls unit in this._UnitDict.Values)
                    unit.DataSaveCapacity(usedPeriods, this.ParentPlanning.PlanningParameters, ref levelCount);
                scope.User = new string[] { "Level.Count = " + levelCount.ToString() };
            }
		}
		/// <summary>
		/// Metoda určí rozmezí období (Od-Do), ve kterém se nachází nějaké záznamy o čerpání ze stavů kapacit, za všechny KPJ.
		/// </summary>
		/// <returns></returns>
		private IntRange _DataSaveGetUsedPeriods()
		{
			IntRange result = IntRange.Empty;
			foreach (CapacityUnitCls unit in this._UnitDict.Values)
			{
				IntRange unitUsing = unit.DataSaveGetUsedPeriods();
				result += unitUsing;
			}
			return result;
		}
		/// <summary>
		/// Ukládání dat do lcs.plan_unit_c_cl_rel
		/// </summary>
		private void _DataSaveCClRel()
		{
			// Vlastní ukládání dat:
            using (var scope = Steward.TraceScopeBegin(Constants.TraceTypeCapacity, "Save.Relation", Constants.TraceKeyWordAplScheduler, "Unit.Count = " + this._UnitDict.Count.ToString()))
            {
                scope.SuppressInnerTrace(null, null, "SQL");
                int relCount = 0;
                foreach (CapacityUnitCls unit in this._UnitDict.Values)
                    unit.DataSaveCClRel(ref relCount);
                scope.User = new string[] { "Relation.Count = " + relCount.ToString() };
            }
		}
		#endregion
	}
	#endregion
}
